package com.wyz.tracing.utils;

import com.wyz.tracing.domain.entity.base.Aggregate;
import com.wyz.tracing.domain.entity.base.Identifier;
import com.wyz.tracing.domain.entity.tracing.different.AggregateDifference;
import com.wyz.tracing.domain.entity.tracing.different.field.AggregareFieldDifference;
import com.wyz.tracing.domain.entity.tracing.different.field.CollectionFieldDifference;
import com.wyz.tracing.domain.entity.tracing.different.field.FieldDifference;
import com.wyz.tracing.domain.entity.tracing.different.field.JavaTypeFieldDifference;
import com.wyz.tracing.enums.DifferenceType;
import lombok.NonNull;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 聚合对比工具类
 * <p>
 * 聚合中只应该出现4种类型的字段
 * 1.实现{@link Aggregate}接口的
 * 2.{@link Collection}
 * 3.Java的常规类型，指int,long,float,double,short,char,byte,boolean基础类和包装类型，以及{@link String}
 * 4.值对象，值对象看做是Java的常规类型，使用{@link Object#equals(Object)}进行比较
 * <p>
 * 聚合中不应该出现Map类型
 *
 * @author wyz
 * @version 1.0
 * @date 2023/4/21
 */
public class DifferentUtils {

    public static <T extends Aggregate<ID>, ID extends Identifier> AggregateDifference<T, ID> different(T snapshot, T aggregate) throws IllegalAccessException {
        // 聚合，快照为null的处理
        DifferenceType basicDifferenceType = basicDifferentType(snapshot, aggregate);
        if (basicDifferenceType != null) {
            return new AggregateDifference<>(snapshot, aggregate, basicDifferenceType);
        }

        // snapshot和aggregate不为null时的处理
        Field[] fields = ReflectionUtils.getFields(aggregate);
        DifferenceType aggregateDifferentType = aggregateDifferentType(fields, snapshot, aggregate);

        AggregateDifference<T, ID> aggregateDifference = new AggregateDifference<>(snapshot, aggregate, aggregateDifferentType);
        Map<String, FieldDifference> fieldDifferences = aggregateDifference.getFieldDifferences();
        setDifferences(snapshot, aggregate, fields, fieldDifferences);
        return aggregateDifference;
    }

    private static <T extends Aggregate<ID>, ID extends Identifier> void setDifferences(T snapshot, T aggregate, Field[] fields, Map<String, FieldDifference> fieldDifferences) throws IllegalAccessException {
        for (Field field : fields) {
            if (Identifier.class.isAssignableFrom(field.getType())) {
                continue;
            }
            String filedName = ReflectionUtils.getFieldName(field);
            field.setAccessible(true);

            Object snapshotValue = snapshot == null ? null : field.get(snapshot);
            Object aggregateValue = aggregate == null ? null : field.get(aggregate);
            if (snapshotValue == null && aggregateValue == null) {
                continue;
            }
            FieldDifference fieldDifference = compareFiled(field, snapshotValue, aggregateValue);
            fieldDifferences.put(filedName, fieldDifference);
        }
    }

    @SuppressWarnings("unchecked")
    private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareFiled(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException {
        ComparableType comparableType = ComparableType.comparableType(aggregateValue == null ? snapshotValue : aggregateValue);
        if (ComparableType.AGGREGATE_TYPE.equals(comparableType)) {
            return compareAggregateType(field, (T) snapshotValue, (T) aggregateValue);
        } else if (ComparableType.COLLECTION_TYPE.equals(comparableType)) {
            return compareCollectionType(field, snapshotValue, aggregateValue);
        } else if (ComparableType.JAVA_TYPE.equals(comparableType)) {
            return compareJavaType(field, snapshotValue, aggregateValue);
        } else {
            throw new UnsupportedOperationException("暂不支持的类型：" + field.getGenericType());
        }
    }

    private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareAggregateType(Field field, T snapshotValue, T aggregateValue) throws IllegalAccessException {
        String filedName = ReflectionUtils.getFieldName(field);
        Type type = field.getGenericType();

        Aggregate<?> notNullValue = snapshotValue == null ? aggregateValue : snapshotValue;
        Field[] entityFields = ReflectionUtils.getFields(notNullValue);
        Identifier id = notNullValue.getId();

        DifferenceType differenceType = aggregateDifferentType(entityFields, snapshotValue, aggregateValue);
        AggregareFieldDifference aggregareFieldDifference = new AggregareFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType, id);
        Map<String, FieldDifference> fieldDifferences = aggregareFieldDifference.getFieldDifferences();
        setDifferences(snapshotValue, aggregateValue, entityFields, fieldDifferences);
        return aggregareFieldDifference;
    }

    /**
     * 集合对比时
     * 根据泛型不同选择不同的对比方式
     *
     * @param field
     * @param snapshotValue
     * @param aggregateValue
     * @return
     */
    @SuppressWarnings("unchecked")
    private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareCollectionType(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException {
        String filedName = ReflectionUtils.getFieldName(field);
        Type type = field.getGenericType();

        // 获取集合的泛型
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Class<?> genericityClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];

        // 处理非Map，非聚合的泛型集合
        if (!Aggregate.class.isAssignableFrom(genericityClass) && !Map.class.isAssignableFrom(genericityClass)) {
            Collection<?> snapshotValues = (Collection<?>) snapshotValue;
            Collection<?> aggregateValues = (Collection<?>) aggregateValue;
            DifferenceType differenceType = collectionDifferentType(genericityClass, snapshotValues, aggregateValues);
            return new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType);
        }

        // 处理泛型为聚合的集合
        Collection<T> snapshotValues = (Collection<T>) snapshotValue;
        Collection<T> aggregateValues = (Collection<T>) aggregateValue;

        Map<Serializable, T> snapshotMap = snapshotValues.stream().collect(Collectors.toMap(snapshot -> snapshot.getId().value(), snapshot -> snapshot));
        Map<Serializable, T> aggregateMap = aggregateValues.stream().collect(Collectors.toMap(aggregate -> aggregate.getId().value(), aggregate -> aggregate));

        CollectionFieldDifference collectionFieldDifference = new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue);

        boolean unchanged = true;
        for (Serializable key : snapshotMap.keySet()) {
            T snapshotElement = snapshotMap.get(key);
            T aggregateElement = aggregateMap.get(key);
            FieldDifference fieldDifferent = compareFiled(field, snapshotElement, aggregateElement);
            unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged;
            collectionFieldDifference.getElementDifference().add(fieldDifferent);
        }
        for (Serializable key : aggregateMap.keySet()) {
            if (snapshotMap.get(key) != null) {
                continue;
            }
            T aggregateElement = aggregateMap.get(key);
            FieldDifference fieldDifferent = compareFiled(field, null, aggregateElement);
            unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged;
            collectionFieldDifference.getElementDifference().add(fieldDifferent);
        }
        if (unchanged) {
            collectionFieldDifference.setDifferenceType(DifferenceType.UNTOUCHED);
        } else {
            collectionFieldDifference.setDifferenceType(DifferenceType.MODIFIED);
        }
        return collectionFieldDifference;
    }

    /**
     * Java类型的字段对比
     *
     * @param field
     * @param snapshotValue
     * @param aggregateValue
     * @return
     */
    private static FieldDifference compareJavaType(Field field, Object snapshotValue, Object aggregateValue) {
        String filedName = ReflectionUtils.getFieldName(field);
        Type type = field.getGenericType();
        DifferenceType differenceType = javaDifferentType(snapshotValue, aggregateValue);
        return new JavaTypeFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType);
    }

    private static DifferenceType basicDifferentType(Object snapshot, Object aggregate) {
        if (snapshot == null && aggregate == null) {
            return DifferenceType.UNTOUCHED;
        }
        if (snapshot == null) {
            return DifferenceType.ADDED;
        }
        if (aggregate == null) {
            return DifferenceType.REMOVED;
        }
        return null;
    }

    /**
     * Java类型的字段比较
     * 使用{@link Object#equals(Object)}方法
     *
     * @param snapshot
     * @param aggregate
     * @return
     */
    public static DifferenceType javaDifferentType(Object snapshot, Object aggregate) {
        DifferenceType differenceType = basicDifferentType(snapshot, aggregate);
        if (differenceType != null) {
            return differenceType;
        }
        // 基础类型可以利用自动装箱来调用equals方法
        if (snapshot.equals(aggregate)) {
            return DifferenceType.UNTOUCHED;
        } else {
            return DifferenceType.MODIFIED;
        }
    }

    /**
     * 聚合类型的字段比较
     *
     * @param fields
     * @param snapshot
     * @param aggregate
     * @param <T>
     * @param <ID>
     * @return
     */
    public static <T extends Aggregate<ID>, ID extends Identifier> DifferenceType aggregateDifferentType(Field[] fields, T snapshot, T aggregate) throws IllegalAccessException {
        DifferenceType differenceType = basicDifferentType(snapshot, aggregate);
        if (differenceType != null) {
            return differenceType;
        }

        boolean unchanged = true;
        for (Field field : fields) {
            field.setAccessible(true);
            if (specialHandingClass(field.getType())) {
                // 如果字段为Aggregate的实现，不修改当前Aggregate的标记
                // 标记会出现在该字段本身
                continue;
            }

            if (Collection.class.isAssignableFrom(field.getType())) {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Class<?> parameterizedClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
                if (Aggregate.class.isAssignableFrom(parameterizedClass) || Map.class.isAssignableFrom(parameterizedClass)) {
                    continue;
                }
            }

            Object aggregateValue = field.get(aggregate);
            Object snapshotValue = field.get(snapshot);
            if (snapshotValue == null && aggregateValue == null) {
                // 快照和目标均空，不执行任何处理
                continue;
            } else if (snapshotValue == null) {
                // 快照为空，目标非空，标记为发生变更
                unchanged = false;
                continue;
            }
            // 快照非空，不需要考虑目标的情况，对比快照与目标
            unchanged = snapshotValue.equals(aggregateValue) & unchanged;
        }
        return unchanged ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
    }

    /**
     * 集合类型的字段比较
     * 考虑空集合的场景，使用{@link CollectionUtils}处理空集合的场景
     * <p>
     * 根据泛型的不同选择不同的对比方式
     * 泛型为Java类型时，使用{@link Object#equals(Object)}进行比较
     * 其余场景不在次方法中比较
     *
     * @param typeArguments
     * @param snapshot
     * @param aggregate
     * @return
     */
    public static DifferenceType collectionDifferentType(Class<?> typeArguments, Collection<?> snapshot, Collection<?> aggregate) {
        if (CollectionUtils.isEmpty(snapshot) && CollectionUtils.isEmpty(aggregate)) {
            return DifferenceType.UNTOUCHED;
        }
        if (CollectionUtils.isEmpty(snapshot)) {
            return DifferenceType.ADDED;
        }
        if (CollectionUtils.isEmpty(aggregate)) {
            return DifferenceType.REMOVED;
        }
        if (specialHandingClass(typeArguments)) {
            return snapshot.size() == aggregate.size() ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
        }
        return snapshot.equals(aggregate) ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
    }

    /**
     * 特殊处理的类型
     * @param clazz
     * @return
     */
    private static boolean specialHandingClass(Class<?> clazz) {
        return Identifier.class.isAssignableFrom(clazz)
                || Aggregate.class.isAssignableFrom(clazz)
                || Collection.class.isAssignableFrom(clazz)
                || Map.class.isAssignableFrom(clazz);
    }

    enum ComparableType {
        /**
         * 聚合类型
         * 实现{@link Aggregate}接口的类型
         */
        AGGREGATE_TYPE(),

        /**
         * 集合类型
         * 实现{@link Collection}接口的类型
         */
        COLLECTION_TYPE(),

        /**
         * Java类型
         * int,long,float,double,short,byte,char,boolean和包装类型
         * 以及String,BigDecimal等
         */
        JAVA_TYPE(),

        /**
         * 该类型暂无处理规则
         */
        OTHER_TYPE();

        public static ComparableType comparableType(@NonNull Object obj) {
            if (obj instanceof Aggregate) {
                return AGGREGATE_TYPE;
            } else if (obj instanceof Collection) {
                return COLLECTION_TYPE;
            } else if (obj instanceof Map) {
                return OTHER_TYPE;
            } else {
                return JAVA_TYPE;
            }
        }
    }
}
